package org.joshsacks.fishies;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class GameBoard {
  public static final int WIDTH = 7;
  public static final int HEIGHT = 9;

  public enum GamePieceType {
    A, B, C, D, E, F
  }
  
  private final GamePieceType[][] board;
  
  public GameBoard() {
    board = new GamePieceType[WIDTH][];
    for (int i = 0; i < WIDTH; i++) {
      board[i] = new GamePieceType[HEIGHT];
    }
  }
  
  public void setPosition(int w, int h, GamePieceType type) {
    board[w][h] = type;
  }
  
  public GamePieceType getPosition(int w, int h) {
    return board[w][h];
  }

  public void addPiece(GamePiece currentPiece) {
    int column = currentPiece.getColumn();
    int y = currentPiece.getBottomRow();
    for (int i = 0; i < GamePiece.PIECE_SIZE; i++) {
      board[column][2 - i + y] = currentPiece.typeAtPosition(i);
    }
  }

  public void clear() {
    for (int w = 0; w< WIDTH; w++) {
      for (int h = 0; h < HEIGHT; h++) {
        board[w][h] = null;
      }
    }
  }
 
  public void removeMatches(MatchSet matchSet) {
    for (MatchSet.Match value : matchSet.matchList) {
      for (int i = 0; i < value.getLength(); i++) {
        board[value.getXCoor(i)][value.getYCoor(i)] = null;
      }
    }    
  }
  
  public MatchSet findMatches() {
    MatchSet matchSet = new MatchSet();
    for (int x = 0; x < WIDTH; x++) {
      for (int y = 0; y < HEIGHT; y++) {
        if (board[x][y] == null) {
          continue;
        }
        checkMatch(matchSet, x, y, Direction.UP);
        checkMatch(matchSet, x, y, Direction.RIGHT);
        checkMatch(matchSet, x, y, Direction.UP_RIGHT);
        checkMatch(matchSet, x, y, Direction.DOWN_RIGHT);
      }
    }   
    
    return matchSet;
  }
  
  private void checkMatch(
          MatchSet matchSet, int x, int y, Direction direction) {
    GamePieceType type = board[x][y];   
    int nx;
    int ny;
    for (ny = y + direction.yInc, nx = x + direction.xInc; 
        ny >= 0 && ny < HEIGHT && nx < WIDTH && board[nx][ny] == type; 
        ny += direction.yInc, nx += direction.xInc);
    nx -= direction.xInc;
    ny -= direction.yInc;
    int width = Math.max(nx - x, ny - y) + 1;    
    if (width > 2) {
      matchSet.add(x, y, direction, width);
    }
  }

  public enum Direction {
    RIGHT(1, 0), UP(0, 1), UP_RIGHT(1, 1), DOWN_RIGHT(1, -1);
        
    public int xInc;
    public int yInc;
    
    private Direction(int xInc, int yInc) {
      this.xInc = xInc;
      this.yInc = yInc;
    }      
  };

  public class MatchSet {
    public class Match {
      private final int[] x;
      private final int[] y;
      private final GamePieceType type;
           
      public Match(int x, int y, Direction direction, int length, GamePieceType type) {
        this.x = new int[length];
        this.y = new int[length];
        this.type = type;
        for (int i = 0; i < length; i++) {
          this.x[i] = x + direction.xInc * i;
          this.y[i] = y + direction.yInc * i;
        }
      }
      
      public Match(int[] x, int[] y, GamePieceType type) {
       this.x = x;
       this.y = y;
       this.type = type;
      }

      public int getXCoor(int i) {
        return x[i];
      }

      public int getYCoor(int i) {
        return y[i];
      }

      public GamePieceType getType() {
        return type;
      }

      public int getLength() {
        return x.length;
      }
      
      public boolean overlapsWith(Match match) {
        if (match.getType() != type) {
          return false;
        }
        
        for (int i = 0; i < x.length; i++) {
          for (int j = 0; j < match.getLength(); j++) {
            if ((x[i] == match.x[j]) && (y[i] == match.y[j])) {
              return true;
            }
          }
        }
        return false;
      }
    }
      
    private final List<Match> matchList = new ArrayList<Match>();
    
    private Match merge(Match match1, Match match2) {
      Set<Integer> pieces = new HashSet<Integer>();

      for (int i = 0; i < match1.getLength(); i++) {
        pieces.add(match1.getXCoor(i) + match1.getYCoor(i) * WIDTH);
      }
      for (int i = 0; i < match2.getLength(); i++) {
        pieces.add(match2.getXCoor(i) + match2.getYCoor(i) * WIDTH);
      }
      
      int[] x = new int[pieces.size()];
      int[] y = new int[pieces.size()];
      int i = 0;
      for (Integer value : pieces) {
        x[i] = value % WIDTH;
        y[i] = value / WIDTH;
        i++;
      }
      return new Match(x, y, match1.getType());
    }

    public void add(int x, int y, Direction direction, int length) {
      GamePieceType type = board[x][y];   
      
      int px = x - direction.xInc;
      int py = y - direction.yInc;
      if (px >= 0 && py >= 0 && py < HEIGHT && type == board[px][py]) {
        return;
      }
      Match newMatch = new Match(x, y, direction, length, type);
      for (Match match : matchList) {
        if (match.overlapsWith(newMatch)) {
          newMatch = merge(newMatch, match);
          matchList.remove(match);
          matchList.add(newMatch);       
          return;
        }
      }
      matchList.add(newMatch);
    }
    
    public boolean isEmpty() {
      return matchList.isEmpty();
    }
    
    public List<Match> getMatches() {
      return matchList;
    }
  }

  public SlideDownSet getSlideSet() {
    boolean needSlide = false;
    int[] lowestEmptyCell = new int[WIDTH];
    
    for (int x = 0; x < WIDTH; x++) {
      boolean foundEmpty = false;
      for (int y = 0; y < HEIGHT; y ++) {
        if (board[x][y] == null) {
          if (!foundEmpty) {
            lowestEmptyCell[x] = y;
          }
          if (needSlide) {
            break;
          }      
          foundEmpty = true;
        } else if (foundEmpty) {
         needSlide = true;
         break;
        }     
      }
    }
    
    if (needSlide) {
      return new SlideDownSet(lowestEmptyCell);
    } else {
      return null;
    }
  }
  
  public void slideDown(SlideDownSet set) {
    for (int x = 0; x < WIDTH; x++) {
      for (int y = set.lowestEmptyCell[x]; y < HEIGHT; y++) {
        board[x][y] = y < HEIGHT - 1 ? board[x][y+1] : null;
      }
    }
  }

  public class SlideDownSet {
    private final int[] lowestEmptyCell;

    public SlideDownSet(int[] lowestEmptyCell) {
      this.lowestEmptyCell = lowestEmptyCell;
    }
    
    public boolean shouldSlide(int x, int y) {
      return y > lowestEmptyCell[x];
    }
  }
}
